The unedited conversation with Chat GPT, concerning almost all of the aspects of this session:
(optional): read this to get a better understanding of the topic:
https://chatgpt.com/share/67f18460-1c1c-8010-bc57-9f3b683ec87a
In order to develop transportation search flow, three DTOs need to be created in the application layer.
📂 Suggested Folder: Application/DTOs/City`
public class CityDto
{
public int Id { get; init; }
public required string Title { get; init; }
}
📂 Suggested Folder: Application/DTOs/Transportation
public class TransportationSearchRequestDto
{
public short? VehicleTypeId { get; init; }
public int? FromCityId { get; init; }
public int? ToCityId { get; init; }
public DateTime? StartDate { get; init; }
public DateTime? EndDate { get; init; }
}
public class TransportationSearchResultDto
{
public long Id { get; init; }
public required string CompanyTitle { get; init; }
public required string FromLocationTitle { get; init; }
public required string ToLocationTitle { get; init; }
public required string FromCityTitle { get; init; }
public required string ToCityTitle { get; init; }
public DateTime StartDateTime { get; init; }
public DateTime? EndDateTime { get; init; }
public decimal Price { get; init; }
}
There are a few things to be add to some repositories for transportation search flow.
📂 Suggested Folder: Domain/Framework/Interfaces/Repositories/
TransportationRepositories
public interface ITransportationRepository : IRepository<Transportation, long>
{
Task<IEnumerable<Transportation>> SearchTransportationsAsync(
short? vehicleTypeId,
int? fromCityId,
int? toCityId,
DateTime? startDate,
DateTime? endDate);
}
📂 Suggested Folder: Infrastructure/Services/Services/TransportationRepositories
public class TransportationRepository :
BaseRepository<ApplicationDBContext, Transportation, long>,
ITransportationRepository
{
public TransportationRepository(ApplicationDBContext dbContext) : base(dbContext)
{
}
public async Task<IEnumerable<Transportation>> SearchTransportationsAsync(
short? vehicleTypeId,
int? fromCityId,
int? toCityId,
DateTime? startDate,
DateTime? endDate)
{
var query = DbContext.Transportations
.Include(x => x.Vehicle)
.Include(x => x.FromLocation).ThenInclude(x => x.City)
.Include(x => x.ToLocation).ThenInclude(x => x.City)
.Include(x => x.Company)
.AsQueryable();
query = query.Where(x => vehicleTypeId == null || x.Vehicle.VehicleTypeId == vehicleTypeId.Value);
query = query.Where(x => fromCityId == null || x.FromLocation.CityId == fromCityId.Value);
query = query.Where(x => toCityId == null || x.ToLocation.CityId == toCityId.Value);
query = query.Where(x => startDate == null || x.StartDateTime.Date == startDate.Value.Date);
query = query.Where(x => endDate == null || (x.EndDateTime.HasValue && x.EndDateTime.Value == endDate.Value.Date));
return await query.ToListAsync();
}
}
Auto Mapper simplifies mapping between aggregates and DTOs in both directions.
MappingProfile
that inherits Profile
, and use it to add configurations for mappings📂 Suggested Folder: Application/Mappers/Profiles
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Transportation, TransportationSearchResultDto>()
.ForMember(dest => dest.CompanyTitle,
opt => opt.MapFrom(src => src.Company.Title))
.ForMember(dest => dest.FromLocationTitle,
opt => opt.MapFrom(src => src.FromLocation.Title))
.ForMember(dest => dest.ToLocationTitle,
opt => opt.MapFrom(src => src.ToLocation.Title))
.ForMember(dest => dest.FromCityTitle,
opt => opt.MapFrom(src => src.FromLocation.City.Title))
.ForMember(dest => dest.ToCityTitle,
opt => opt.MapFrom(src => src.ToLocation.City.Title));
CreateMap<City, CityDto>();
}
}
Program.cs
.
.
.
builder.Services.AddAutoMapper(typeof(MappingProfile));
var app = builder.Build();
.
.
.
ResultStatus
enum and Result
classResult is a template to transfer data between services and controllers (in backend), so will use a generic type
public class Result<T>
{
public ResultStatus Status { get; set; }
public string? ErrorMessage { get; set; }
public T? Data { get; set; }
public bool IsSuccess => Status == ResultStatus.Success;
public static Result<T> Success(T data)
{
return new Result<T>
{
Status = ResultStatus.Success,
Data = data
};
}
public static Result<T> Error(T data)
{
return new Result<T>
{
Status = ResultStatus.Error,
Data = data
};
}
public static Result<T> NotFound(T data)
{
return new Result<T>
{
Status = ResultStatus.NotFound,
Data = data
};
}
}
As you can see, there's a property of type ResultStatus, which is a enum for status of request
public enum ResultStatus
{
Success,
NotFound,
ValidationError,
Conflict,
Unauthorized,
Forbidden,
Error
}
You can read more about enums: W3Schools
IService
& ServiceNow, use I[Entity]Repositry
and IUnitOfWork
in services to implement business logic
I[Entity]Service
and [Entity]Service
which implements it📂 Suggested Folder for I[Entity]Service
: Application/Interfaces
📂 Suggested Folder for Services: Application/Services
An example of I[Entity]Service
:
public interface ITransportationService
{
Task<Result<IEnumerable<TransportationSearchResultDto>>> SearchTransportationsAsync(TransportationSearchRequestDto searchRequest);
}
An example of [Entity]Service
:
public class TransportationService : ITransportationService
{
private readonly ITransportationRepository _transportationRepository;
private readonly IMapper _mapper;
private readonly IUnitOfWork _unitOfWork;
public TransportationService(ITransportationRepository transportationRepository, IMapper mapper, IUnitOfWork unitOfWork)
{
_transportationRepository = transportationRepository;
_mapper = mapper;
_unitOfWork = unitOfWork;
}
public async Task<Result<IEnumerable<TransportationSearchResultDto>>> SearchTransportationsAsync(TransportationSearchRequestDto requestDto)
{
var result = await _transportationRepository.SearchTransportationsAsync(
vehicleTypeId: requestDto.VehicleTypeId,
fromCityId: requestDto.FromCityId,
toCityId: requestDto.ToCityId,
startDateTime: requestDto.StartDate,
endDateTime: requestDto.EndDate);
if (result.Any())
{
var dto = _mapper.Map<IEnumerable<TransportationSearchResultDto>>(result);
return Result<IEnumerable<TransportationSearchResultDto>>.Success(dto);
}
return Result<IEnumerable<TransportationSearchResultDto>>.NotFound(null);
}
}
Program.cs
:.
.
.
builder.Services.AddScoped<ITransportationService, TransportationService>();
builder.Services.AddScoped<ICityService, CityService>();
.
.
.
Now we're getting to endpoints, you should communicate with client side through web-api. So every controller uses Services in Application layer to receive requests and send responses with DTOs.
You should use [ApiController]
attribute on top of them, route them and handle different status codes. TransportationController:
[ApiController]
[Route("api/[controller]")]
public class TransportationController : ControllerBase
{
private readonly ITransportationService _transportationService;
public TransportationController(ITransportationService transportationService)
{
_transportationService = transportationService;
}
[HttpGet("search")]
public async Task<IActionResult> SearchTransportations([FromQuery] TransportationSearchRequestDto searchRequest)
{
if (searchRequest == null)
{
return BadRequest("Invalid search request");
}
var result = await _transportationService.SearchTransportationsAsync(searchRequest);
if (result.IsSuccess)
{
return Ok(result);
}
// any unsuccessful status
return result.Status switch
{
ResultStatus.NotFound => NotFound(result.ErrorMessage),
ResultStatus.ValidationError => BadRequest(result.ErrorMessage),
_ => StatusCode(500, result.ErrorMessage),
};
}
}
For testing purposes, add some data into the related tables.
You are provided with a SQL script, that adds some sample data into the following tables
Important Notes: Note that different database names, and different table names will produce errors while executing the script. Consider adjusting these names before executing the script
Cities
Companies
LocationTypes
Locations
VehicleTypes
Vehicles
Transportation
Open TransportationRelatedSampleData.sql
with SSMS, and execute the query